import { UniversalCamera } from "@babylonjs/core/Cameras/universalCamera";
import { Camera } from "@babylonjs/core/Cameras/camera";
import { PointerInfo } from "@babylonjs/core/Events/pointerEvents";
import { Vector2, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { AnimationFrame } from "../animation/animation-frame";
import { Scene } from "@babylonjs/core/scene";
import { FreeCameraTouchInput } from '@babylonjs/core/Cameras/Inputs/freeCameraTouchInput';
import { ArcRotateCamera } from "@babylonjs/core/Cameras/arcRotateCamera";
import { IPointerEvent } from "@babylonjs/core/Events/deviceInputEvents";
import { BabylonContainer } from "../base/babylon-container";
import { Helper } from "../tool/helper";


/**
 * 键盘按键
 * @param KEYBOARD_W W键
 * @param KEYBOARD_S S键
 * @param KEYBOARD_A A键
 * @param KEYBOARD_D D键
 * @param KEYBOARD_UP 方向UP键
 * @param KEYBOARD_DOWN 方向DOWN键
 * @param KEYBOARD_LEFT 方向LEFT键
 * @param KEYBOARD_RIGHT 方向RIGHT键
 */
const KEY = {
    KEYBOARD_W: 87,
    KEYBOARD_S: 83,
    KEYBOARD_A: 65,
    KEYBOARD_D: 68,
    KEYBOARD_UP: 38,
    KEYBOARD_DOWN: 40,
    KEYBOARD_LEFT: 37,
    KEYBOARD_RIGHT: 39,
};


interface IPressHandle {
    (isLeft: boolean, vertical: number, deltaX: number, deltaY: number): void
}


/**
 * 相机输入控制预设
 * @param scene 所在场景
 */
class CameraInputPreset {

    private _pressStart = false;
    private _pointerStart = new Vector2();
    private _req = {};
    private _dir = new Vector3();
    private _ucPointerHandle: { (e: PointerInfo): void };
    private _acPointerHandle: { (e: PointerInfo): void };
    private _camChangeHandle: { (): void };
    private _pressHandle?: IPressHandle;
    private _camera: Camera | null = null;
    private _deltaX = 0;
    private _deltaY = 0;
    private _currentPointerId: number | null = null;
    private _delay = {};
    private _angularSensibility: number | null = null;
    private _angularSensibilityA: number | null = null;
    private _frameThreshold = 10;
    private _distanceThreshold = 0.01;
    private _meddle = true;

    public readonly scene: Scene;

    constructor(scene: Scene) {
        this.scene = scene;
        this._camChangeHandle = () => { this.setSceneCamera() };
        this._ucPointerHandle = (e) => { this.cameraPointerModify(e) };
        this._acPointerHandle = (e) => { this.cameraPointerModify(e) };

        this.setSceneCamera();
        scene.onActiveCameraChanged.add(this._camChangeHandle);
    }

    /**
     * 是否修改触点行为
     */
    public set meddle(enabled: boolean) {
        this._meddle = enabled;
    }

    /**
     * 设置长按距离阈值
     */
    public set distanceThreshold(distance: number) {
        this._distanceThreshold = distance;
    }

    /**
     * 设置长按时间阈值（按帧）
     */
    public set frameThreshold(frame: number) {
        this._frameThreshold = frame;
    }

    /**
     * 设置相机输入
     */
    private setSceneCamera(): void {
        if (!Helper.isCameraGeneral(this.scene.activeCamera)) {
            console.warn('camera-input-preset needs a camera that marked as general (see Helper.markGeneralCamera) !');
            return;
        }
        if (this._camera && this._camera.uniqueId === this.scene.activeCamera?.uniqueId) return;

        this._pressStart = false;
        AnimationFrame.cancel(this._req);
        let cam = this._camera;
        if (cam) {
            if (cam instanceof UniversalCamera) {
                this.setUniversalCamera(cam, true);
            } else if (cam instanceof ArcRotateCamera) {
                this.setArcRotateCamera(cam, true);
            }
        }
        this._camera = this.scene.activeCamera;
        cam = this._camera;
        if (cam instanceof UniversalCamera) {
            this._pressHandle = this.universalCameraPointerMove;
            this.setUniversalCamera(cam);
        } else if (cam instanceof ArcRotateCamera) {
            this._pressHandle = this.arcRotateCameraPointerZoom;
            this.setArcRotateCamera(cam);
        }
    }

    /**
     * 当相机为arcRotateCamera时的设置
     * @param camera 相机
     * @param recover 是否还原为默认设置
     */
    private setArcRotateCamera(camera: ArcRotateCamera, recover?: boolean): void {
        if (recover) {
            camera.keysUp = [KEY.KEYBOARD_UP];
            camera.keysDown = [KEY.KEYBOARD_DOWN];
            camera.keysLeft = [KEY.KEYBOARD_LEFT];
            camera.keysRight = [KEY.KEYBOARD_RIGHT];
            this.scene.onPointerObservable.removeCallback(this._acPointerHandle);
        } else {
            camera.keysUp = [KEY.KEYBOARD_UP, KEY.KEYBOARD_W];
            camera.keysDown = [KEY.KEYBOARD_DOWN, KEY.KEYBOARD_S];
            camera.keysLeft = [KEY.KEYBOARD_LEFT, KEY.KEYBOARD_A];
            camera.keysRight = [KEY.KEYBOARD_RIGHT, KEY.KEYBOARD_D];
            this.scene.onPointerObservable.add(this._acPointerHandle);
        }
    }

    /**
     * 当相机为universalCamera时的设置
     * @param camera 相机
     * @param recover 是否还原为默认设置
     */
    private setUniversalCamera(camera: UniversalCamera, recover?: boolean): void {
        if (recover) {
            camera.keysUp = [KEY.KEYBOARD_UP];
            camera.keysDown = [KEY.KEYBOARD_DOWN];
            camera.keysLeft = [KEY.KEYBOARD_LEFT];
            camera.keysRight = [KEY.KEYBOARD_RIGHT];
            (camera.inputs.attached['touch'] as FreeCameraTouchInput).singleFingerRotate = false;
            this.scene.onPointerObservable.removeCallback(this._ucPointerHandle);
        } else {
            camera.keysUp = [KEY.KEYBOARD_UP, KEY.KEYBOARD_W];
            camera.keysDown = [KEY.KEYBOARD_DOWN, KEY.KEYBOARD_S];
            camera.keysLeft = [KEY.KEYBOARD_LEFT, KEY.KEYBOARD_A];
            camera.keysRight = [KEY.KEYBOARD_RIGHT, KEY.KEYBOARD_D];
            (camera.inputs.attached['touch'] as FreeCameraTouchInput).singleFingerRotate = true;
            this.scene.onPointerObservable.add(this._ucPointerHandle);
        }
    }

    /**
     * 修改触点输入行为
     * @param e 触点信息
     */
    private cameraPointerModify(e: PointerInfo): void {
        if (!this._meddle) {
            this.pressEnd(e.event as IPointerEvent);
            return;
        }
        const evt = e.event as IPointerEvent;
        if ((evt as any).pointerType !== 'touch') return;
        switch (evt.type) {
            case 'pointerdown':
                this.pressStart(evt);
                break;
            case 'pointermove':
                this.checkPress(evt);
                break;
            case 'pointerup':
                this.pressEnd(evt);
                break;
        }
    }

    /**
     * 当相机为arcRotateCamera时的缩放行为（头盔内普通模式单触点缩放）
     * @param isLeft 是否左半屏
     * @param vertical 纵向值
     * @param deltaX x向偏移值
     * @param deltaY y向偏移值
     */
    private arcRotateCameraPointerZoom(isLeft: boolean, vertical: number, deltaX: number, deltaY: number): void {
        if (!isLeft) return;
        const cam = this._camera as ArcRotateCamera;
        if (this._angularSensibility === null) {
            this._angularSensibility = cam.angularSensibilityX;
            this._angularSensibilityA = cam.angularSensibilityY;
            cam.angularSensibilityX = 1e12;
            cam.angularSensibilityY = 1e12;
        }
        AnimationFrame.cancel(this._delay);
        AnimationFrame.throttleByFrame(this._delay, 1, () => {
            if (this._angularSensibility !== null) {
                cam.angularSensibilityX = this._angularSensibility;
                cam.angularSensibilityY = this._angularSensibilityA!;
            }
            this._angularSensibility = null;
            this._angularSensibilityA = null;
        }, false);
        const engine = cam.getEngine();
        const spd = cam.speed * Math.sqrt(engine.getDeltaTime() / (engine.getFps() * 100));
        const sensibility = cam.panningSensibility * 0.001;

        const speedZ = spd * Math.abs(vertical) * sensibility;
        if (vertical > 0) {
            cam.radius += speedZ;
            cam.radius = Math.min(cam.radius, cam.maxZ);
        } else {
            cam.radius -= speedZ;
            cam.radius = Math.max(cam.radius, cam.minZ);
        }

        const dir = this._dir;
        const speedX = spd * Math.abs(deltaX) * sensibility;
        if (deltaX < 0) {
            cam.getDirectionToRef(Vector3.RightReadOnly, dir);
            dir.scaleInPlace(speedX);
            cam.target.addInPlace(dir);
        } else {
            cam.getDirectionToRef(Vector3.LeftReadOnly, dir);
            dir.scaleInPlace(speedX);
            cam.target.addInPlace(dir);
        }
        cam.cameraDirection.addInPlace(dir);

        const speedY = spd * Math.abs(deltaY) * sensibility;
        if (deltaY > 0) {
            cam.getDirectionToRef(Vector3.UpReadOnly, dir);
            dir.scaleInPlace(speedY);
            cam.target.addInPlace(dir);
        } else {
            cam.getDirectionToRef(Vector3.UpReadOnly, dir);
            dir.scaleInPlace(-speedY);
            cam.target.addInPlace(dir);
        }
        cam.cameraDirection.addInPlace(dir);

        cam.update();
    }

    /**
     * 当相机为universalCamera时的触点移动行为（单触点移动）
     * @param isLeft 是否左半屏
     * @param vertical 是否上半屏
     * @param deltaX x向偏移值
     * @param deltaY y向偏移值
     */
    private universalCameraPointerMove(isLeft: boolean, vertical: number, deltaX: number, deltaY: number): void {
        if (!isLeft) return;
        const cam = this._camera as UniversalCamera;
        if (this._angularSensibility === null) {
            this._angularSensibility = cam.touchAngularSensibility;
            cam.touchAngularSensibility = 1e12;
        }
        AnimationFrame.cancel(this._delay);
        AnimationFrame.throttleByFrame(this._delay, 1, () => {
            if (this._angularSensibility !== null) cam.touchAngularSensibility = this._angularSensibility;
            this._angularSensibility = null;
        }, false);

        const dir = this._dir;
        const engine = cam.getEngine();
        const spd = cam.speed * Math.sqrt(engine.getDeltaTime() / (engine.getFps() * 100));
        const sensibility = cam.touchMoveSensibility * 0.01;

        const speedX = spd * Math.abs(deltaX) * sensibility;
        if (deltaX > 0) {
            cam.getDirectionToRef(Vector3.RightReadOnly, dir);
            dir.scaleInPlace(speedX);
            cam.position.addInPlace(dir);
        } else {
            cam.getDirectionToRef(Vector3.LeftReadOnly, dir);
            dir.scaleInPlace(speedX);
            cam.position.addInPlace(dir);
        }
        cam.cameraDirection.addInPlace(dir);

        const speedY = spd * Math.abs(deltaY) * sensibility;
        if (deltaY < 0) {
            cam.getDirectionToRef(BabylonContainer.forward, dir);
            dir.scaleInPlace(speedY);
            cam.position.addInPlace(dir);
        } else {
            cam.getDirectionToRef(BabylonContainer.forward, dir);
            dir.scaleInPlace(-speedY);
            cam.position.addInPlace(dir);
        }
        cam.cameraDirection.addInPlace(dir);
        cam.update();
    }

    /**
     * 按下开始触点行为
     * @param evt 事件
     */
    private pressStart(evt: IPointerEvent): void {
        if (this._pressStart) return;
        this._currentPointerId = evt.pointerId;
        this._pressStart = true;
        this._pointerStart.set(evt.clientX, evt.clientY);
        const vertical = (evt.clientY - evt.target.clientHeight * 0.5) / evt.target.clientHeight * 2;
        const isLeft = evt.target.clientWidth * 0.5 > evt.clientX;
        let count = 0;
        let vtc = 0;
        AnimationFrame.request(() => {
            count++;
            vtc = vertical;
            if (count < this._frameThreshold || Math.abs(this._deltaX) > this._distanceThreshold || Math.abs(this._deltaY) > this._distanceThreshold) {
                vtc = 0;
            }
            if (this._pressHandle) this._pressHandle(isLeft, vtc, this._deltaX, this._deltaY);
        }, 0, 200, this._req);
    }

    /**
     * 检测按下状态（区别滑动）
     * @param evt 事件
     */
    private checkPress(evt: IPointerEvent): void {
        if (!this._pressStart) return;
        if (evt.pointerId !== this._currentPointerId) return;
        const s = Math.min(evt.target.clientWidth, evt.target.clientHeight);
        const dx = evt.clientX - this._pointerStart.x;
        const dy = evt.clientY - this._pointerStart.y;
        this._deltaX = dx / s;
        this._deltaY = dy / s;
    }

    /**
     * 按下结束 
     */
    private pressEnd(evt: IPointerEvent): void {
        if (!this._pressStart) return;
        if (evt.pointerId !== this._currentPointerId) return;
        this._currentPointerId = null;
        this._pressStart = false;
        this._deltaX = 0;
        this._deltaY = 0;
        AnimationFrame.cancel(this._req);
    }

}


export { CameraInputPreset };